ART学习总结


一、概述

ART是Android平台上的新一代运行时,用来代替dalvik。它主要采用了AOT的方法,在apk安装的时候将dalvikbytecode一次性编译成arm本地指令(但是这种AOT与c语言等还是有本质不同的,还是需要虚拟机的环境支持),这样在运行的时候就无需进行任何解释或编译便可直接执行,节省了运行时间,提高了效率,但是在一定程度上使得安装的时间变长,空间占用变大。(本文所有时序图均基于Android M)


二、OAT文件

简单的说,oat文件是嵌套在一个elf文件的格式中的。在elf文件的动态符号表中有三个重要的符号:oatdata、oatexec、oatlastword,分别表示oat的数据区,oat文件中的native code和结束位置。

guoqifa@guoqifa:~$ readelf -s system@priv-app@Mms@Mms.apk@classes.dex
Symbol table '.dynsym' contains 4 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000001000 0x95b000 OBJECT  GLOBAL DEFAULT    4 oatdata
     2: 000000000095c000 0xb3a4c0 OBJECT  GLOBAL DEFAULT    5 oatexec
     3: 00000000014964bc     4 OBJECT  GLOBAL DEFAULT    5 oatlastword

这些关系结构在图中说明的很清楚,简单理解就是在oatdata中,保存有原来的dex文件内容,在头部还保留了寻址到dex文件内容的偏移地址和指向对应的oat class偏移,oat class中还保存了对应的native code的偏移地址,这样也就间接的完成了dexbytecode和native code的对应关系。具体请参考:Android ART Oat文件格式简析(上)Android ART Oat文件格式简析(下)Android运行时ART加载OAT文件的过程分析. 也可自行分析 art\runtime\Oat_file.cc文件中OatFile::Setup()函数来进一步学习OAT文件。(下图来源老罗博客)

oat文件格式

三、编译

apk在安装时使用dex2oat来编译。大概流程如下图所示,有两种后端编译模式,可选的是所谓快模式(Quick)或优化模式(Optimizing)。如果没有特别指定的话,编译镜像image使用快模式,而编译一般的应用程序则使用优化模式。

dex2oat compile

什么是编译器的后端呢?其实这是LLVM引入的概念。所有的程序先用前端翻译成中间表示层,然后进行优化,最后用后端将优化过的中间表示层代码编译成平台相关的代码。Android虽然没有直接用LLVM编译器(以前也确实用过,但从6.0开始就废弃掉了),但是借鉴了这种编译器的设计结构。值得一提的是,如果使用优化模式,则一定是用PIC模式进行编译。

更具体的compile时序图如下(该时序图只绘出quick编译模式),可自行分析该流程。


Compile Image

四、类加载

ART类的加载通过ClassLinker::DefineClass()函数来完成。该函数会分别调用InsertClass()、LoadClass()、LinkClass()来执行类的加载。

InsertClass()主要是把该类插入class_table_中,方便下次FindClass时直接从LookupClass()中返回结果。LoadClass()用来从指定的DEX文件中加载指定的类,并初始化一个Class对象,Class对象包含了一系列的ArtField对象和ArtMethod对象。LinkClass()用来动态绑定虚函数和接口函数。

类加载完成后,得到的是一个Class对象。这个Class对象关联有一系列的ArtField对象和ArtMethod对象。其中,ArtField对象描述的是成员变量,而ArtMethod对象描述的是成员函数。对于每一个ArtMethod对象,它都有一个解释器入口点和一个本地机器指令入口点。这样,无论一个类方法是通过解释器执行,还是直接以本地机器指令执行,我们都可以以统一的方式来进行调用。

关于类的加载可参考Android运行时ART加载类和方法的过程分析

Load Class

五、方法调用

Zygote孵化应用进程后便会从入口AndroidRuntime.start()进入运行时,进入Java世界。

CallStaticVoidMethod

ART方法调用如上图所示,最终调用到ArtMethod::Invoke()。ArtMethod正是前面类加载章节中提到的ArtMethod对象,“它都有一个解释器入口点和一个本地机器指令入口点”,Invoke()函数正是通过art_quick_invoke_stub()或art_quick_invoke_static_stub()来调用oat文件中的native code的。art_quick_invoke_stub()是平台相关的汇编函数,比如我的机器该函数定义在art/runtime/arch/arm64/quick_entrypoints_arm64.S文件中。更多方法调用的分析请参考Android运行时ART执行类方法的过程分析.

总结:通过查找相关的oat文件,得到所需要的类和方法,并将其对应的native code的位置放入ArtMethod结构,最后通过Invoke成员完成调用。


六、JNI调用

1. Java调用native方法

ArtMethod对象与真实执行的代码链接的过程主要是通过LinkCode()函数执行的。

void ClassLinker::LinkCode(ArtMethod* method, const OatFile::OatClass* oat_class,
                           uint32_t class_def_method_index) {
  ......
  if (method->IsNative()) {
    // Unregistering restores the dlsym lookup stub.
    method->UnregisterNative();
   ......
}
void ArtMethod::UnregisterNative() {
  CHECK(IsNative() && !IsFastNative()) << PrettyMethod(this);
  // restore stub to lookup native pointer via dlsym
  RegisterNative(GetJniDlsymLookupStub(), false);
}

GetJniDlsymLookupStub()函数返回平台相关的art_jni_dlsym_lookup_stub()汇编函数指针,RegisterNative()将该汇编函数指针注册到ArtMethod中,以上是链接过程。

待JNI调用时便会执行到汇编art_jni_dlsym_lookup_stub()函数,该函数会继续调用artFindNativeMethod()函数。

art_jni_dlsym_lookup_stub
extern "C" void* artFindNativeMethod(Thread* self) {
  DCHECK_EQ(self, Thread::Current());
#endif
  Locks::mutator_lock_->AssertNotHeld(self);  // We come here as Native.
  ScopedObjectAccess soa(self);

  ArtMethod* method = self->GetCurrentMethod(nullptr);
  DCHECK(method != nullptr);

  // Lookup symbol address for method, on failure we'll return null with an exception set,
  // otherwise we return the address of the method we found.
  void* native_code = soa.Vm()->FindCodeForNativeMethod(method);
  if (native_code == nullptr) {
    DCHECK(self->IsExceptionPending());
    return nullptr;
  } else {
    // Register so that future calls don't come here
    method->RegisterNative(native_code, false);
    return native_code;
  }
}

artFindNativeMethod()函数就是查找到相应方法的native code,然后再次注册到ArtMethod中,这样以后再执行的时候就直接跳到了native code执行了。

2. native调用Java

native调用Java是通过JNIEnv->FindClass()、JNIEnv->GetStaticMethodID()、JNIEnv->CallVoidMethod()来查找类,得到相应的方法的ID,然后通过此ID去调用。最终如上面第五章方法调用时序图所示,调用的是ArtMethod::Invoke()。即JNI的这些API其实还是做了一遍ART的类加载和初始化及调用的过程。


七、参考文章

1.也来看看Android的ART运行时
2.Android运行时ART简要介绍和学习计划
3.Android ART Oat文件格式简析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容